package com.qx.grainmall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.qx.grainmall.product.vo.Category2Vo;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qx.common.utils.PageUtils;
import com.qx.common.utils.Query;

import com.qx.grainmall.product.dao.CategoryDao;
import com.qx.grainmall.product.entity.CategoryEntity;
import com.qx.grainmall.product.service.CategoryService;
import org.thymeleaf.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
     RedissonClient redissonClient;
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public Map<Long, List<Category2Vo>> catalogListCache() {
        //先去缓存查
        String catalogListCache = redisTemplate.opsForValue().get("catalogListCache");

        //缓存有 直接返回
        if(catalogListCache != null){
            Map<Long, List<Category2Vo>> longListMap = JSON.parseObject(catalogListCache, new TypeReference<Map<Long, List<Category2Vo>>>() {
            });
            System.out.println("缓存命中");
            return longListMap;
        }else {
            //redis中没有 就去数据库中查找
            Map<Long, List<Category2Vo>> dbmap= catelog();
            String s = JSON.toJSONString(dbmap);

            //如果数据库查到也是null
            if(s == null  || StringUtils.isEmpty(s)){
                //缓存穿透 短暂缓存 null值
                redisTemplate.opsForValue().set("catalogListCache",s,30, TimeUnit.SECONDS);
            }

            //加入缓存时间  随机值解决雪崩的 问题
            Random random = new Random();
            int i = random.nextInt(300);
            //查完放入缓存中
            redisTemplate.opsForValue().set("catalogListCache",s,300+i,TimeUnit.SECONDS);
            return dbmap;
        }
    }

    @Override
    public Map<Long, List<Category2Vo>> getCatalogJsonFromDbWithRedisLock() {
        //先去缓存 查询
        String catalogListCache = redisTemplate.opsForValue().get("catalogListCache");

        //缓存有的话直接返回
        if(catalogListCache != null){
            Map<Long, List<Category2Vo>> longListMap = JSON.parseObject(catalogListCache, new TypeReference<Map<Long, List<Category2Vo>>>() {
            });

//            System.out.println("从缓存中获取数据,缓存名字~~");
            return longListMap;
        }else {
            //去redis 缓存中占锁
            String uuid = UUID.randomUUID().toString();
            ValueOperations<String, String> ops = redisTemplate.opsForValue();
            //占坑 给时间 实现 原子性操作
            Boolean lock = ops.setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
            if(lock){ //获取锁 成功
                try {
                    //执行业务
                    //判断 redis中有没有 并且 去数据库查询
                    System.out.println("获取分布式锁 成功");
                    Map<Long, List<Category2Vo>> dbmap= catelog();
                    String s = JSON.toJSONString(dbmap);
                    System.out.println("去数据查询~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
                    // 查询到的也是空 多并发的穿透
                    if(s == null || StringUtils.isEmpty(s)){
                        //缓存 穿透  解决方式 存入redis中 赋空值
                        redisTemplate.opsForValue().set("catalogListCache",s,30,TimeUnit.SECONDS);
                    }
                    //加入缓存 时间 随机值   多并发的雪崩问题
                    Random random = new Random();
                    int i = random.nextInt(300);

                    //查完放入缓存中
                    redisTemplate.opsForValue().set("catalogListCache",s,300+i,TimeUnit.SECONDS);

                    return dbmap;

                }finally {
                    // 删除锁原子性 删除锁对比uuid 防止刚查到uuid还没有来的及对比,锁过期，对比成功删除锁，删别的锁
                    //保证删锁原子性 使用lua脚本
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    //删除锁
                    redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
                }
            }else { //没有设置锁 自旋等待

                System.out.println("获取分布式锁 失败,等待中~~~~~~~~~~");
                //加锁失败  重试的机制
                //休眠一百毫秒后 重试
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return getCatalogJsonFromDbWithRedisLock();
            }
        }

    }

    //redisson读写锁
    @Override
    public Map<Long, List<Category2Vo>> getCatalogJsonWithReadWriteLock() {
        RReadWriteLock threeCatelog = redissonClient.getReadWriteLock("threeCatelog");
        //读锁
        RLock rLock = threeCatelog.readLock();
        //写锁
        RLock wLock = threeCatelog.writeLock();
        String catalogListCache = null;
        //加读锁
        rLock.lock(10,TimeUnit.SECONDS);
        try {
            System.out.println("读锁加锁成功"+Thread.currentThread().getId());
            //先去缓存查询
            catalogListCache = redisTemplate.opsForValue().get("catalogListCache");
        }finally {
            //释放锁
            rLock.unlock();
        }
        //判断redis中有无数据
        if(catalogListCache!= null){
            Map<Long, List<Category2Vo>> longListMap = JSON.parseObject(catalogListCache, new TypeReference<Map<Long, List<Category2Vo>>>() {
            });
            System.out.println("缓存命中");
            return longListMap;
        }else { //缓存为空
            //加写锁
            wLock.lock(30,TimeUnit.SECONDS);
            try {
                //再次去缓存查询 判断是否为空
                catalogListCache = redisTemplate.opsForValue().get("catalogListCache");
                if(catalogListCache != null){
                    Map<Long, List<Category2Vo>> longListMap = JSON.parseObject(catalogListCache, new TypeReference<Map<Long, List<Category2Vo>>>() {
                    });
                    System.out.println("写锁缓存命中"+ Thread.currentThread().getId());
                    return longListMap;
                }
                //redis没有 去数据库查询
                Map<Long, List<Category2Vo>> catelog= catelog();
                String s = JSON.toJSONString(catelog);
                //如果数据库查询的也是 kong 缓存穿透
                if(s ==null ||StringUtils.isEmpty(s)){
                    redisTemplate.opsForValue().set(catalogListCache,s,30,TimeUnit.SECONDS);
                }
                //加入缓存时间 防止雪崩
                Random random = new Random();
                int i = random.nextInt(300);
                //查完放入缓存中
                redisTemplate.opsForValue().set("catalogListCache",s,300+i,TimeUnit.SECONDS);
                return catelog;
            }finally {
                wLock.unlock();//释放写锁
            }

        }

    }


    @Override
    public Map<Long, List<Category2Vo>> catelog() {
        List<CategoryEntity> l1List = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        System.out.println("进数据库查询");
        Map<Long,List<Category2Vo>> map = new HashMap();

        for(CategoryEntity l1 : l1List) {
            List<CategoryEntity> l2List = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l1.getCatId()));
            List<Category2Vo> l2VoList = l2List.stream().map(l2 -> {
                Category2Vo catalog2Vo = new Category2Vo();
                catalog2Vo.setCatalog1Id(l1.getCatId());
                catalog2Vo.setId(l2.getCatId());
                catalog2Vo.setName(l2.getName());
                List<CategoryEntity> l3List = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
                List<Category2Vo.Category3Vo> collect = l3List.stream().map(l3 -> {
                    Category2Vo.Category3Vo catalog3Vo = new Category2Vo.Category3Vo();
                    catalog3Vo.setName(l3.getName());
                    catalog3Vo.setId(l3.getCatId());
                    catalog3Vo.setCatalog2Id(l2.getCatId());
                    return catalog3Vo;
                }).collect(Collectors.toList());
                catalog2Vo.setCatalog3List(collect);
                return catalog2Vo;
            }).collect(Collectors.toList());
            map.put(l1.getCatId(),l2VoList);
        }
        return map;
    }
}